﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using DapperExtensions.Sql;
using DapperExtensions.Mapper;
using static Dapper.Contrib.Extensions.SqlMapperExtensions;
using System.ComponentModel;
using Dapper;

namespace DapperExtensions
{
    public static class DapperExtensions
    {
        private readonly static object _lock = new object();

        private static readonly ISqlDialect DefaultSqlDialect = new SqlServerDialect();
        private static readonly Dictionary<string, ISqlDialect> SqlDialectDictionary
            = new Dictionary<string, ISqlDialect>
            {
                ["sqlconnection"] = new SqlServerDialect(),
                ["sqlceconnection"] = new SqlCeDialect(),
                ["npgsqlconnection"] = new PostgreSqlDialect(),
                ["sqliteconnection"] = new SqliteDialect(),
                ["mysqlconnection"] = new MySqlDialect(),
                ["oracleconnection"] = new OracleDialect()
            };

        public static GetDatabaseTypeDelegate GetDatabaseType;

        private static IDapperImplementor GetInstance()
        {
            return new DapperImplementor(new SqlGeneratorImpl(new DapperExtensionsConfiguration(DefaultSqlDialect)));
        }

        private static IDapperImplementor GetInstance(IDbConnection connection)
        {
            var name = GetDatabaseType?.Invoke(connection).ToLower()
                       ?? connection.GetType().Name.ToLower();

            ISqlDialect sqlDialect;
            bool has = SqlDialectDictionary.TryGetValue(name, out sqlDialect);
            if (has)
            {
                return new DapperImplementor(new SqlGeneratorImpl(new DapperExtensionsConfiguration(sqlDialect)));
            }
            else
            {
                return new DapperImplementor(new SqlGeneratorImpl(new DapperExtensionsConfiguration(DefaultSqlDialect)));
            }
        }

        /// <summary>
        /// Executes a query for the specified id, returning the data typed as per T
        /// </summary>
        public static T Get<T>(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null, IDapperImplementor instance = null) where T : class
        {
            instance = instance ?? GetInstance(connection);
            var result = instance.Get<T>(connection, id, transaction, commandTimeout);
            return (T)result;
        }

        /// <summary>
        /// Executes an insert query for the specified entity.
        /// </summary>
        public static void Insert<T>(this IDbConnection connection, IEnumerable<T> entities, IDbTransaction transaction = null, int? commandTimeout = null, IDapperImplementor instance = null) where T : class
        {
            instance = instance ?? GetInstance(connection);
            instance.Insert<T>(connection, entities, transaction, commandTimeout);
        }

        /// <summary>
        /// Executes an insert query for the specified entity, returning the primary key.  
        /// If the entity has a single key, just the value is returned.  
        /// If the entity has a composite key, an IDictionary&lt;string, object&gt; is returned with the key values.
        /// The key value for the entity will also be updated if the KeyType is a Guid or Identity.
        /// </summary>
        public static dynamic Insert<T>(this IDbConnection connection, T entity, IDbTransaction transaction = null, int? commandTimeout = null, IDapperImplementor instance = null) where T : class
        {
            instance = instance ?? GetInstance(connection);
            return instance.Insert<T>(connection, entity, transaction, commandTimeout);
        }

        /// <summary>
        /// Executes an update query for the specified entity.
        /// </summary>
        public static bool Update<T>(this IDbConnection connection, T entity, IDbTransaction transaction = null, int? commandTimeout = null, bool ignoreAllKeyProperties = false, IDapperImplementor instance = null) where T : class
        {
            instance = instance ?? GetInstance(connection);
            return instance.Update<T>(connection, entity, transaction, commandTimeout, ignoreAllKeyProperties);
        }

        /// <summary>
        /// Updates table T with the values in param.
        /// The table must have a key named "Id" and the value of id must be included in the "param" anon object. The Id value is used as the "where" clause in the generated SQL
        /// </summary>
        /// <typeparam name="T">Type to update. Translates to table name</typeparam>
        /// <param name="connection"></param>
        /// <param name="param">An anonymous object with key=value types</param>
        /// <returns>The Id of the updated row. If no row was updated or id was not part of fields, returns null</returns>
        public static bool UpdateFields<T>(this IDbConnection connection, object param, IDbTransaction transaction = null, int? commandTimeOut = null, IDapperImplementor instance = null) where T : class
        {
            instance = instance ?? GetInstance(connection);
            return instance.UpdateFields<T>(connection, param, transaction, commandTimeOut);
        }

        /// <summary>
        /// Executes a delete query for the specified entity.
        /// </summary>
        public static bool Delete<T>(this IDbConnection connection, T entity, IDbTransaction transaction = null, int? commandTimeout = null, IDapperImplementor instance = null) where T : class
        {
            instance = instance ?? GetInstance(connection);
            return instance.Delete<T>(connection, entity, transaction, commandTimeout);
        }

        /// <summary>
        /// Executes a delete query using the specified predicate.
        /// </summary>
        public static bool Delete<T>(this IDbConnection connection, object predicate, IDbTransaction transaction = null, int? commandTimeout = null, IDapperImplementor instance = null) where T : class
        {
            instance = instance ?? GetInstance(connection);
            return instance.Delete<T>(connection, predicate, transaction, commandTimeout);
        }

        /// <summary>
        /// Executes a select query using the specified predicate, returning an IEnumerable data typed as per T.
        /// </summary>
        public static IEnumerable<T> GetList<T>(this IDbConnection connection, object predicate = null, IList<ISort> sort = null, IDbTransaction transaction = null, int? commandTimeout = null, bool buffered = false, IDapperImplementor instance = null) where T : class
        {
            instance = instance ?? GetInstance(connection);
            return instance.GetList<T>(connection, predicate, sort, transaction, commandTimeout, buffered);
        }

        /// <summary>
        /// Executes a select query using the specified predicate, returning an IEnumerable data typed as per T.
        /// Data returned is dependent upon the specified page and resultsPerPage.
        /// </summary>
        public static IEnumerable<T> GetPage<T>(this IDbConnection connection, object predicate, IList<ISort> sort, int page, int resultsPerPage, IDbTransaction transaction = null, int? commandTimeout = null, bool buffered = false, IDapperImplementor instance = null) where T : class
        {
            instance = instance ?? GetInstance(connection);
            return instance.GetPage<T>(connection, predicate, sort, page, resultsPerPage, transaction, commandTimeout, buffered);
        }

        /// <summary>
        /// Executes a select query using the specified predicate, returning an IEnumerable data typed as per T.
        /// Data returned is dependent upon the specified firstResult and maxResults.
        /// </summary>
        public static IEnumerable<T> GetSet<T>(this IDbConnection connection, object predicate, IList<ISort> sort, int firstResult, int maxResults, IDbTransaction transaction = null, int? commandTimeout = null, bool buffered = false, IDapperImplementor instance = null) where T : class
        {
            instance = instance ?? GetInstance(connection);
            return instance.GetSet<T>(connection, predicate, sort, firstResult, maxResults, transaction, commandTimeout, buffered);
        }

        /// <summary>
        /// Executes a query using the specified predicate, returning an integer that represents the number of rows that match the query.
        /// </summary>
        public static int Count<T>(this IDbConnection connection, object predicate, IDbTransaction transaction = null, int? commandTimeout = null, IDapperImplementor instance = null) where T : class
        {
            instance = instance ?? GetInstance(connection);
            return instance.Count<T>(connection, predicate, transaction, commandTimeout);
        }

        /// <summary>
        /// Executes a select query for multiple objects, returning IMultipleResultReader for each predicate.
        /// </summary>
        public static IMultipleResultReader GetMultiple(this IDbConnection connection, GetMultiplePredicate predicate, IDbTransaction transaction = null, int? commandTimeout = null, IDapperImplementor instance = null)
        {
            instance = instance ?? GetInstance(connection);
            return instance.GetMultiple(connection, predicate, transaction, commandTimeout);
        }

        /// <summary>
        /// Gets the appropriate mapper for the specified type T. 
        /// If the mapper for the type is not yet created, a new mapper is generated from the mapper type specifed by DefaultMapper.
        /// </summary>
        public static IClassMapper GetMap<T>() where T : class
        {
            return GetInstance().SqlGenerator.Configuration.GetMap<T>();
        }

        /// <summary>
        /// Clears the ClassMappers for each type.
        /// 和数据库无关
        /// </summary>
        public static void ClearCache()
        {
            GetInstance().SqlGenerator.Configuration.ClearCache();
        }

        /// <summary>
        /// Generates a COMB Guid which solves the fragmented index issue.
        /// See: http://davybrion.com/blog/2009/05/using-the-guidcomb-identifier-strategy
        /// 和数据库类型无关
        /// </summary>
        public static Guid GetNextGuid()
        {
            return GetInstance().SqlGenerator.Configuration.GetNextGuid();
        }
    }
}
